#!/usr/bin/python3
# -*- coding: utf-8 -*-

from datetime import datetime

from colorama import Fore, Style, init as init_colorama

from .datetime import now
from .validators import reject_if, reject_if_null

CALLBACK_PROGRESS = 0
CALLBACK_INFO = 1
CALLBACK_VERBOSE = 2
CALLBACK_SUCCESS = 3
CALLBACK_TITLE = 4
CALLBACK_WARN = 5
CALLBACK_ERROR = 6


class CallbackLogger(object):
    """ 回调接口

    """

    def close(self):
        pass

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    @staticmethod
    def fmt_err(
            exc_info: tuple
    ):
        """ 格式化当前截获的异常信息。

        :param exc_info: 包含三个元素的元组，应当来自于`sys.exc_info`函数。
        :return: 格式化后的字符串。
        """
        s = ''
        if exc_info:
            etype, value, tb = exc_info
            if etype and value:
                s += '{etype}: {value}'.format(etype=etype.__name__, value=value)
            if tb and tb.tb_frame and tb.tb_frame.f_code:
                tb_frame = tb.tb_frame
                f_code = tb_frame.f_code
                s += '\n  at {file}:{lineno}'.format(file=f_code.co_filename, lineno=tb_frame.f_lineno)

        return s

    def log(
            self,
            level: int,
            s: (str, int, float,),
            kwargs: dict = None,
            *,
            timestamp: datetime = None
    ):
        """记录日志。

        :param level: 日志级别。
        :param s: 日志的内容。
        :param kwargs: 格式化日志内容的参数。
        :param timestamp: 日志的时间戳。
        """
        reject_if_null(timestamp)

        if level in (CALLBACK_WARN, CALLBACK_ERROR,):
            from sys import exc_info
            # 对于警告和错误级别的日志，加入异常信息。
            if kwargs:
                kwargs['err'] = self.fmt_err(exc_info=exc_info())
        if isinstance(s, (int, float)):
            self.log_(level, s, timestamp)
        else:
            msg = s.format_map(kwargs) if kwargs else s
            self.log_(level, msg, timestamp)

    def log_(
            self,
            level: int,
            msg,
            timestamp: datetime
    ):
        raise NotImplementedError()

    def info(
            self,
            s: str,
            **kwargs
    ):
        self.log(CALLBACK_INFO, s, kwargs, timestamp=now())

    def verbose(
            self,
            s: str,
            **kwargs
    ):
        self.log(CALLBACK_VERBOSE, s, kwargs, timestamp=now())

    def success(
            self,
            s: str,
            **kwargs
    ):
        self.log(CALLBACK_SUCCESS, s, kwargs, timestamp=now())

    def title(
            self,
            s: str,
            **kwargs
    ):
        self.log(CALLBACK_TITLE, s, kwargs, timestamp=now())

    def warn(
            self,
            s: str,
            **kwargs
    ):
        self.log(CALLBACK_WARN, s, kwargs, timestamp=now())

    def error(
            self,
            s: str,
            **kwargs
    ):
        self.log(CALLBACK_ERROR, s, kwargs, timestamp=now())

    def progress(
            self,
            value: float
    ) -> None:
        """显示操作成功的消息。

        :param value: 进度百分比，从`0`到`100`。
        """
        reject_if(lambda: value < 0 or value > 100)

        self.log(CALLBACK_PROGRESS, value, timestamp=now())


class DebugCallback(CallbackLogger):
    """ 用于调试的回调类。
    """
    __slots__ = ('__status',)

    def __init__(self):
        init_colorama()
        self.__status = 0

    def close(self):
        print(Style.RESET_ALL, end='')
        if self.__status == 2:
            # 如果最近一次输出了进度条，那么打印额外的换行符。
            print()
        self.__status = 0

    def log_(
            self,
            level: int,
            msg,
            timestamp: datetime
    ):
        dp = timestamp.strftime('%Y-%m-%d %H:%M:%S %z')

        if self.__status == 0:
            # 如果从未输出过日志，那么打印额外的换行符。
            print()
        elif self.__status == 2 and level != CALLBACK_PROGRESS:
            # 如果最近一次输出了进度条，已输出完毕，那么打印额外的换行符。
            print()

        if level == CALLBACK_INFO:
            print(Fore.BLUE + Style.BRIGHT + dp + ' [INFO   ] ' + msg + Style.RESET_ALL)
            self.__status = 1
        elif level == CALLBACK_VERBOSE:
            print(Fore.BLACK + Style.BRIGHT + dp + ' [VERBOSE] ' + msg + Style.RESET_ALL)
            self.__status = 1
        elif level == CALLBACK_SUCCESS:
            print(Fore.GREEN + Style.BRIGHT + dp + ' [SUCCESS] ' + msg + Style.RESET_ALL)
            self.__status = 1
        elif level == CALLBACK_TITLE:
            print(Fore.WHITE + Style.BRIGHT + dp + ' [TITLE  ] ' + msg + Style.RESET_ALL)
            self.__status = 1
        elif level == CALLBACK_WARN:
            print(Fore.YELLOW + Style.BRIGHT + dp + ' [WARN   ] ' + msg + Style.RESET_ALL)
            self.__status = 1
        elif level == CALLBACK_ERROR:
            print(Fore.RED + Style.BRIGHT + dp + ' [ERROR  ] ' + msg + Style.RESET_ALL)
            self.__status = 1
        elif level == CALLBACK_PROGRESS:
            head = '({value:-5.1%})'.format(value=msg / 100) if msg < 100 else '( 100%)'
            detail = '=' * int(msg / 2)
            detail = '[' + detail.ljust(50, ' ') + ']'
            print(Fore.WHITE + Style.DIM + head, end='')
            print(Style.RESET_ALL + ' ', end='')
            if msg < 100:
                print(Fore.BLUE + Style.DIM + detail + Style.RESET_ALL, end='\r')
            else:
                print(Fore.GREEN + Style.DIM + detail + Style.RESET_ALL, end='\r')

            self.__status = 2
        else:
            print(Fore.BLACK + Style.BRIGHT + '<----- unknown level ----->')
            self.__status = 1
